#include <arpa/inet.h>  // inet_pton
#include <assert.h>
#include <dirent.h>
#include <fcntl.h>          // open
#include <pthread.h>
#include <regex.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <syslog.h>
#include <sys/socket.h>
#include <sys/stat.h>       // fstat
#include <sys/time.h>
#include <sys/types.h>  // open
#include <time.h>
#include <unistd.h>
#include "logx.h"
#include "ringqueue.h"

#define MAX_QUEUE_SIZE  32

// 文件名长度
#define MAX_FILE_PATH_LEN 128

// 日志最大长度
#define MAX_MSG_LEN     256

typedef struct {
    log_cfg_t       cfg;
    int                 fd;             // 输出fd
    size_t          curr_file_size;
    struct sockaddr_in dest_addr;

    pthread_t consume_thread;
    int consumer_quit;
    // 队列对象：保证多线程环境不串行
    rqueue_t *buf;
} run_ctx_t;

static run_ctx_t g_ctx = {
    .cfg.level = LOG_LEVEL_INFO,
    .consumer_quit = 0
};


// 拉取并写出日志
void *consumer(void *arg);

static const char *level_str(int level)
{
    switch (level) {
        case LOG_LEVEL_DEBUG:
            return "DBG";
        case LOG_LEVEL_INFO:
            return "INF";
        case LOG_LEVEL_WARN:
            return "WAR";
        case LOG_LEVEL_ERR:
            return "ERR";
        case LOG_LEVEL_FATAL:
            return "FTL";
        default:
            return "XXX";
    }
}



static int term_init(log_term_cfg_t *cfg)
{
    // 指向标准输出
    g_ctx.fd = 1;
    return 0;
}

static char * fill_file_path(char *file_path, const char *dir, const char *file_pattern, int index)
{
    int total, n;
    char *p, suffix[8] = {0};

    // 填充文件名
    total = n = snprintf(file_path,
             MAX_FILE_PATH_LEN, "%s%s",
             dir,
             file_pattern);
    if (index) {
        if ((p = strrchr(file_path, '.'))) {
            n = p - file_path;
            strncpy(suffix, p+1, total - n - 1);
        }
        n += snprintf(&file_path[n], MAX_FILE_PATH_LEN-n, "%d", index);
        if (strlen(suffix))
            snprintf(&file_path[n], MAX_FILE_PATH_LEN-n, ".%s", suffix);
    }

    return file_path;
}

static int file_init(log_file_cfg_t *cfg)
{
    char file_name[MAX_FILE_PATH_LEN];
    struct stat stat;

    // 若目录不存在，创建
    if (access(cfg->dir, F_OK) == -1) {
        //printf("mkdir('%s', 00777)\n", cfg->dir);
        if (mkdir(cfg->dir, 00777) == -1) {
            printf("failed to create diretory for log file: %s\n", cfg->dir);
            return -1;
        }
    }

    // 填充文件名
    fill_file_path(file_name, cfg->dir, cfg->file_pattern, 0);

    // 只写，追加，不存在时创建
    printf("log file: %s\n", file_name);
    g_ctx.fd = open(file_name, O_WRONLY | O_APPEND | O_CREAT, 00666);
    if (g_ctx.fd == -1) {
        perror("open");
        return -2;
    }

    // 更新当前已写入大小
    fstat(g_ctx.fd, &stat);
    g_ctx.curr_file_size = stat.st_size;
    printf("curr_file_size: %ld\n", g_ctx.curr_file_size);

    return 0;
}

static int net_init(log_net_cfg_t *cfg)
{
    g_ctx.fd = socket(AF_INET, SOCK_DGRAM, 0);
    if (g_ctx.fd == -1) {
        perror("socket");
        return -1;
    }

    g_ctx.dest_addr.sin_family = AF_INET;
    g_ctx.dest_addr.sin_port = htons(cfg->dest_port);
#if 1
    if (inet_pton(AF_INET, cfg->dest_addr, &g_ctx.dest_addr.sin_addr) == -1) {
        perror("inet_pton");
        close(g_ctx.fd);
        return -2;
    }
#else
    g_ctx.dest_addr.sin_addr.s_addr = inet_addr(cfg->dest_addr);
#endif

    int sndbuf = 400*1024;
    socklen_t size = sizeof(sndbuf);
    if (setsockopt(g_ctx.fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, size) == -1) {
        perror("setsockopt");
    }

    if (getsockopt(g_ctx.fd, SOL_SOCKET, SO_SNDBUF, &sndbuf, &size) == 0) {
        //printf("now sndbuf: %d\n", sndbuf);
    }
    return 0;
}


int clear_dirty_file(log_file_cfg_t *cfg)
{
    int ret = 0;
    char *p, base[256] = {0}, suffix[256] = {0}, file_regex_patt[256];
    char file_path[256];
    DIR *log_dir = NULL;
    struct dirent *ent = NULL;
    char back_no[16] = {0};
    regex_t file_reg;
    regmatch_t rmatch[2];

    p = strrchr(cfg->file_pattern, '.');
    memcpy(base, cfg->file_pattern, p - cfg->file_pattern);
    strcpy(suffix, p);

    /*
     * ^iot([0-9]*).log$
     * match: iot.log iot1.log iot111.log
     * mismatch: aiot.log  iotx.log iot.log.bk
     */
    snprintf(file_regex_patt, sizeof(file_regex_patt), "^%s([0-9]*)%s$", base, suffix);
    if (regcomp(&file_reg, file_regex_patt, REG_EXTENDED) != 0) {
        printf("regcomp filed, regex: %s\n", file_regex_patt);
        return 0;
    }

    log_dir = opendir(cfg->dir);
    if (log_dir) {
        while ((ent = readdir(log_dir))) {
            if (ent->d_type == DT_DIR) continue;
            memset(rmatch, 0, sizeof(rmatch));
            if (regexec(&file_reg, ent->d_name, 2, rmatch, 0) == 0) {
                // match and so==eo, means the current log file
                if (rmatch[1].rm_so == rmatch[1].rm_eo) {
                    continue;
                }
                memset(back_no, 0, sizeof(back_no));
                memcpy(back_no,
                         ent->d_name + rmatch[1].rm_so,
                         rmatch[1].rm_eo - rmatch[1].rm_so);
                // back file in permit ranges
                if (strtol(back_no, NULL, 10) <= cfg->backup_size) {
                    continue;
                }
            }

            // delete all other files
            snprintf(file_path, sizeof(file_path), "%s%s", cfg->dir, ent->d_name);
            remove(file_path);
        }
        closedir(log_dir);
        ret = 1;
    }
    regfree(&file_reg);
    return ret;
}

int logx_init(log_cfg_t *log_cfg)
{
    int ret = 0;

    g_ctx.cfg = *log_cfg;

    if (g_ctx.cfg.dest == LOG_DEST_FILE) {
        // 确保文件扩展名存在
        if (!strrchr(g_ctx.cfg.u.file_cfg.file_pattern, '.'))
            strcat(g_ctx.cfg.u.file_cfg.file_pattern, ".log");
        // 统一格式：确保目录尾字符为/
        if (g_ctx.cfg.u.file_cfg.dir[strlen(g_ctx.cfg.u.file_cfg.dir)-1] != '/') {
            strcat(g_ctx.cfg.u.file_cfg.dir, "/");
        }
    clear_dirty_file(&g_ctx.cfg.u.file_cfg);
    }

    switch (log_cfg->dest) {
        case LOG_DEST_TERM:
            ret = term_init(&g_ctx.cfg.u.term_cfg);
            break;
        case LOG_DEST_FILE:
            ret = file_init(&g_ctx.cfg.u.file_cfg);
            break;
        case LOG_DEST_NET:
            ret = net_init(&g_ctx.cfg.u.net_cfg);
            break;
        default:
            return -1;
            break;
    }
    g_ctx.buf = rqueue_init(MAX_QUEUE_SIZE);
    if (g_ctx.buf == NULL) {
        if (log_cfg->dest != LOG_DEST_TERM)
            close(g_ctx.fd);
        return -3;
    }

    ret = pthread_create(&g_ctx.consume_thread, NULL, consumer, &g_ctx);
    if (ret) {
        printf("create consumer thread failed: %s\n", strerror(ret));
        rqueue_destroy(&g_ctx.buf, 0);
        if (log_cfg->dest != LOG_DEST_TERM)
            close(g_ctx.fd);
        return -4;
    }

    return ret;
}

void logx_set_level(int level)
{
    if (level >= LOG_LEVEL_DEBUG && level <= LOG_LEVEL_FATAL)
        g_ctx.cfg.level = level;
}

void log_to_syslog(int level, char *log_info)
{
    int priority;
    char *p = NULL;

    switch (level) {
        case LOG_LEVEL_DEBUG:
            priority = LOG_DEBUG;
            break;
        case LOG_LEVEL_INFO:
            priority = LOG_NOTICE;
            break;
        case LOG_LEVEL_WARN:
            priority = LOG_WARNING;
            break;
        case LOG_LEVEL_ERR:
            priority = LOG_ERR;
            break;
        case LOG_LEVEL_FATAL:
        default:
            priority = LOG_CRIT;
            break;
    }
    // trim '\n' in tail for syslog do line break automate
    if ((p = strrchr(log_info, '\n')))
        *p = '\0';
    syslog(priority, "%s", log_info);
}

int logx_msg(int level, const char *file, int line, const char *fmt, ...)
{
    if (level < g_ctx.cfg.level) return 0;

    time_t t;
    struct tm tm;
    struct timeval tv;
    char fmt_str[128] = {0};
    char *msg = NULL;
    va_list ap;

    gettimeofday(&tv, NULL);
    t = tv.tv_sec;
    localtime_r(&t, &tm);
    snprintf(fmt_str, sizeof(fmt_str), "%02d:%02d:%02d.%03ld %s:%05d [%s] %s",
            tm.tm_hour, tm.tm_min, tm.tm_sec, tv.tv_usec/1000, file, line, level_str(level), fmt);

    msg = calloc(1, MAX_MSG_LEN);
    assert(msg);
    if (msg) {
        va_start(ap, fmt);
        vsnprintf(msg, MAX_MSG_LEN, fmt_str, ap);
        //printf("put: %p\n", msg);
        rqueue_put(g_ctx.buf, (void *)msg, -1);
        va_end(ap);

        if (g_ctx.cfg.syslog_on)
            log_to_syslog(level, msg);
        return 1;
    }

    return 0;
}

static void output_to_terminal(run_ctx_t *ctx, char *msg)
{
    //fputs(msg, stderr);
    write(ctx->fd, msg, strlen(msg));
}

static void rotate_file(log_file_cfg_t *cfg)
{
    int i;
    char file_path[MAX_FILE_PATH_LEN], to_file_path[MAX_FILE_PATH_LEN];
    char *from_file = NULL, *to_file = NULL;


    fill_file_path(file_path, cfg->dir, cfg->file_pattern, cfg->backup_size);
    remove(file_path);

    for (i=cfg->backup_size-1; i>=0; i--) {
        from_file = fill_file_path(file_path, cfg->dir, cfg->file_pattern, i);

        to_file = fill_file_path(to_file_path, cfg->dir, cfg->file_pattern, i+1);
        //printf("log file rename('%s', '%s')\n", from_file, to_file);
        rename(from_file, to_file);
    }

    file_init(cfg);
}


static void output_to_file(run_ctx_t *ctx, char *msg)
{
    int num_w = strlen(msg);

    if (num_w + ctx->curr_file_size > ctx->cfg.u.file_cfg.file_size) {
        close(ctx->fd);
        rotate_file(&ctx->cfg.u.file_cfg);
    }
    write(ctx->fd, msg, num_w);
    ctx->curr_file_size += num_w;
}


static void output_to_net(run_ctx_t *ctx, char *msg)
{
    sendto(ctx->fd, msg, strlen(msg), 0, (struct sockaddr *)&ctx->dest_addr, sizeof(ctx->dest_addr));
}


void *consumer(void *arg)
{
    char *msg = NULL;
    int dest;
    run_ctx_t *ctx = (run_ctx_t *)arg;

    dest = ctx->cfg.dest;

    while (!ctx->consumer_quit) {
        msg = rqueue_get(ctx->buf, -1);
        if (!msg) {
            printf("consumer get failed \n");
            continue;
        }

        //printf("got %p\n", msg);
        switch (dest) {
        case LOG_DEST_TERM:
            output_to_terminal(ctx, msg);
            break;
        case LOG_DEST_FILE:
            output_to_file(ctx, msg);
            break;
        case LOG_DEST_NET:
            output_to_net(ctx, msg);
            break;
        }

        free(msg);
    }

    //printf("consumer thread quit\n");

    return (void *)0;
}

void logx_destroy()
{
    while (rqueue_size(g_ctx.buf)) {
        //printf("  wait rqueue_get() clear buffer\n");
        usleep(20*1000);
    }

    g_ctx.consumer_quit = 1;
    rqueue_put(g_ctx.buf, strdup("log quit\n"), -1); // 确保consumer能进行循环测试
    sleep(1);

    rqueue_destroy(&g_ctx.buf, 1);

    if (g_ctx.cfg.dest != LOG_DEST_TERM)
        close(g_ctx.fd);
}

